using System;
using System.Collections;
using System.IO;
using System.Collections.Generic;

namespace System.Windows.Media.DirectShow.Vorbis
{

    public abstract class Residue
    {

        protected int begin, end;
        protected int partitionSize; // grouping
        protected int classifications; // partitions
        protected int classBook; // groupbook
        protected int[] cascade; // secondstages
        protected int[,] books;
        protected Dictionary<Mode, ResidueLook> looks = new Dictionary<Mode, ResidueLook>();

        protected Residue()
        {
        }

        public Residue(IBitStream source, SetupHeader header)
        {
            begin = source.ReadInt32(24);
            end = source.ReadInt32(24);
            partitionSize = source.ReadInt32(24) + 1;
            classifications = source.ReadInt32(6) + 1;
            classBook = source.ReadInt32(8);

            cascade = new int[classifications];

            int acc = 0;

            for (int i = 0; i < classifications; i++)
            {
                int highBits = 0, lowBits = 0;
                lowBits = source.ReadInt32(3);
                if (source.ReadBit())
                {
                    highBits = source.ReadInt32(5);
                }
                cascade[i] = (highBits << 3) | lowBits;
                acc += Util.Icount(cascade[i]);
            }

            books = new int[classifications, 8];

            for (int i = 0; i < classifications; i++)
            {
                for (int j = 0; j < 8; j++)
                {
                    if ((cascade[i] & (1 << j)) != 0)
                    {
                        books[i, j] = source.ReadInt32(8);
                        if (books[i, j] > header.CodeBooks.Length)
                        {
                            throw new VorbisFormatException("Reference to invalid codebook entry in residue header.");
                        }
                    }
                }
            }
        }


        public static Residue CreateInstance(IBitStream source, SetupHeader header)
        {

            int type = source.ReadInt32(16);
            switch (type)
            {
                case 0:
                    throw new VorbisFormatException("Residue type " + type + " is not supported.");
                case 1:
                    return new Residue2(source, header);
                case 2:
                    //System.out.println("residue type 2");
                    return new Residue2(source, header);
                default:
                    throw new VorbisFormatException("Residue type " + type + " is not supported.");
            }
        }

        public abstract int Type { get; }

        public abstract void DecodeResidue(VorbisStream vorbis, IBitStream source, Mode mode, int ch, bool[] doNotDecodeFlags, float[][] vectors);
        //public abstract double[][] getDecodedVectors();

        public int Begin
        {
            get
            {
                return begin;
            }
        }

        public int End
        {
            get
            {
                return end;
            }
        }

        public int PartitionSize
        {
            get
            {
                return partitionSize;
            }
        }

        public int Classifications
        {
            get
            {
                return classifications;
            }
        }

        public int ClassBook
        {
            get
            {
                return classBook;
            }
        }

        public int[] Cascade
        {
            get
            {
                return cascade;
            }
        }

        public int[,] Books
        {
            get
            {
                return books;
            }
        }

        protected void Fill(Residue clone)
        {
            clone.begin = begin;
            clone.books = books;
            clone.cascade = cascade;
            clone.classBook = classBook;
            clone.classifications = classifications;
            clone.end = end;
            clone.partitionSize = partitionSize;
        }

        protected ResidueLook GetLook(VorbisStream source, Mode key)
        {
            //return new Look(source, key);
            ResidueLook look = null;
            if (!looks.TryGetValue(key, out look))
            {
                look = new ResidueLook(this, source, key);
                looks.Add(key, look);
            }
            return look;
        }
    }

    public class ResidueLook
    {
        private int map;
        private int parts;
        private int stages;
        private CodeBook[] fullbooks;
        private CodeBook phrasebook;
        private int[][] partbooks;
        private int partvals;
        private int[][] decodemap;
        //private int postbits;
        //private int phrasebits;
        //private int frames;
        private Residue residue;

        public ResidueLook(Residue residue, VorbisStream source, Mode mode)
        {
            int dim = 0, maxstage = 0;

            this.residue = residue;
            map = mode.Mapping;
            parts = residue.Classifications;
            fullbooks = source.SetupHeader.CodeBooks;
            phrasebook = fullbooks[residue.ClassBook];
            dim = phrasebook.Dimensions;

            partbooks = new int[parts][];

            for (int j = 0; j < parts; j++)
            {
                uint stg = Util.Ilog(residue.Cascade[j]);
                if (stg != 0)
                {
                    if (stg > maxstage)
                    {
                        maxstage = (int)stg;
                    }
                    partbooks[j] = new int[stg];
                    for (int k = 0; k < stg; k++)
                    {
                        if ((residue.Cascade[j] & (1 << k)) != 0)
                        {
                            partbooks[j][k] = residue.Books[j, k];
                        }
                    }
                }
            }

            partvals = (int)Math.Round(Math.Pow(parts, dim));
            stages = maxstage;

            decodemap = new int[partvals][];

            for (int j = 0; j < partvals; j++)
            {
                int val = j;
                int mult = partvals / parts;
                decodemap[j] = new int[dim];

                for (int k = 0; k < dim; k++)
                {
                    int deco = val / mult;
                    val -= deco * mult;
                    mult /= parts;
                    decodemap[j][k] = deco;
                }
            }
        }

        public int[][] DecodeMap
        {
            get
            {
                return decodemap;
            }
        }

        //public int Frames {
        //	get {
        //   	return frames;
        //   }
        //}

        public int Map
        {
            get
            {
                return map;
            }
        }

        public int[][] PartBooks
        {
            get
            {
                return partbooks;
            }
        }

        public int Parts
        {
            get
            {
                return parts;
            }
        }

        public int PartVals
        {
            get
            {
                return partvals;
            }
        }

        //public int PhraseBits {
        //	get {
        //   	return phrasebits;
        //   }
        //}

        public CodeBook PhraseBook
        {
            get
            {
                return phrasebook;
            }
        }

        //public int PostBits {
        //	get {
        //   	return postbits;
        //   }
        //}

        public int Stages
        {
            get
            {
                return stages;
            }
        }
    }



    public class Residue2 : Residue
    {

        private Residue2()
        {
        }

        public Residue2(IBitStream source, SetupHeader header)
            : base(source, header)
        {
        }

        public override int Type
        {
            get
            {
                return 2;
            }
        }

        public override void DecodeResidue(VorbisStream vorbis, IBitStream source, Mode mode, int ch, bool[] doNotDecodeFlags, float[][] vectors)
        {

            ResidueLook look = GetLook(vorbis, mode);

            CodeBook codeBook = vorbis.SetupHeader.CodeBooks[ClassBook];

            int classvalsPerCodeword = codeBook.Dimensions;
            int nToRead = End - Begin;
            int partitionsToRead = nToRead / PartitionSize; // partvals

            int samplesPerPartition = PartitionSize;
            int partitionsPerWord = look.PhraseBook.Dimensions;

            int partWords = (partitionsToRead + partitionsPerWord - 1) / partitionsPerWord;

            int realCh = 0;
            for (int i = 0; i < doNotDecodeFlags.Length; i++)
            {
                if (!doNotDecodeFlags[i])
                {
                    realCh++;
                }
            }

            float[][] realVectors = new float[realCh][];

            realCh = 0;
            for (int i = 0; i < doNotDecodeFlags.Length; i++)
            {
                if (!doNotDecodeFlags[i])
                {
                    realVectors[realCh++] = vectors[i];
                }
            }

            int[][] partword = new int[partWords][];
            for (int s = 0; s < look.Stages; s++)
            {
                for (int i = 0, l = 0; i < partitionsToRead; l++)
                {
                    if (s == 0)
                    {
                        int temp = look.PhraseBook.ReadInt32(source);
                        if (temp == -1)
                        {
                            throw new VorbisFormatException("");
                        }
                        partword[l] = look.DecodeMap[temp];
                        if (partword[l] == null)
                        {
                            throw new VorbisFormatException("");
                        }
                    }

                    for (int k = 0; k < partitionsPerWord && i < partitionsToRead; k++, i++)
                    {
                        int offset = begin + i * samplesPerPartition;
                        if ((cascade[partword[l][k]] & (1 << s)) != 0)
                        {
                            CodeBook stagebook = vorbis.SetupHeader.CodeBooks[look.PartBooks[partword[l][k]][s]];
                            if (stagebook != null)
                            {
                                stagebook.ReadVvAdd(realVectors, source, offset, samplesPerPartition);
                            }
                        }
                    }
                }
            }
        }


        public Object Clone()
        {
            Residue2 clone = new Residue2();
            Fill(clone);
            return clone;
        }

    }


}